/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_time.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

#include "ola_timer.h"
#include "ola_pal_os.h"
#include "ola_error.h"
#include "ola_log.h"
#include "ola_pal_os.h"
#include "ola_def.h"
#include "securec.h"
#include "ola_base_utils.h"
#include "ola_pal_time.h"
#include "ola_pal_os.h"


/* 一个闰年轮回的总天数(365+365+365+366) */
#define   OLA_TOTAL_DAY_OF_FOUR_YEARS          1461

#define   OLA_MONTH_JANUARY                    1
#define   OLA_MONTH_FEBRUARY                   2
#define   OLA_MONTHS_PER_YEAR                  12
#define   OLA_HOURS_PER_DAY                    24
#define   OLA_MINUTES_PER_HOUR                 60
#define   OLA_SECONDS_PER_MINUTE               60

typedef struct {
    /* 是否同步 */
    bool timeSynced;
    OlaMutex mutex;
    /* 上一次同步的utc时间 */
    unsigned long long lastSyncUtcTime;
    /* 上一次的系统时间，OlaGetTime获取 */
    unsigned long long lastSystemTime;
    /* 时间同步之后的回调函数 */
    OlaFirstTimeSyncCallbak func;
    /* 回调函数参数 */
    void *param;
} TimeParams;

const unsigned long long ULLONG_MAX_VALUE = (unsigned long long)(-1);

static TimeParams g_timeParams = {0};
/* 判断时间同步模块有没有进行初始化 */
static bool g_isInited = false;

/*
 * 通过年月日解析出星期
 * 返回0表示星期日，以此类推，星期六为6
 * 返回0成功，返回非0失败
 */
static int OlaGetWeekDayFromDate(unsigned short year, unsigned char month, unsigned char day, unsigned char *outweek)
{
    if (outweek == NULL) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    /* 1/2月份进行转换 */
    if ((month == OLA_MONTH_JANUARY) || (month == OLA_MONTH_FEBRUARY)) {
        month += OLA_MONTHS_PER_YEAR;
        year--;
    }

    if (month > (OLA_MONTHS_PER_YEAR + OLA_MONTH_FEBRUARY)) {
        OlaLogWarning("month error");
        return OLA_TIME_ERR_MONTH_INVALID;
    }
    /* 利用基姆拉尔森公式计算星期,数字为公式中的常量 */
    *outweek = (day + 2 * month + 3 * (month + 1) / 5 + year + year / 4 - year / 100 + year / 400) % 7;

    return OLA_OK;
}


/*
 * UTC时间溢出判断
 * 返回0成功，返回非0失败
 */
static int OlaIsUtcDeltaTimeOverflow(unsigned long long deltaTime)
{
    if (deltaTime > (ULLONG_MAX_VALUE - g_timeParams.lastSyncUtcTime)) {
        /* 溢出表示收到时区数据非法，直接返回，此次不同步本地时间 */
        OlaLogWarning("time overflow");
        return OLA_TIME_ERR_TIME_OVERFLOW;
    }
    return OLA_OK;
}

/* 时间同步模块初始化 */
int OlaTimeInit(void)
{
    if (g_isInited == true) {
        return OLA_OK;
    }
    (void)memset_s(&g_timeParams, sizeof(g_timeParams), 0, sizeof(g_timeParams));

    g_timeParams.mutex = OlaMutexCreate();
    if (g_timeParams.mutex == NULL) {
        OlaLogWarning("create mutex error");
        return OLA_TIME_ERR_CREATE_MUTEX;
    }
    g_isInited = true;

    return OLA_OK;
}

/*
 * 判断是否需要初始化
 * 返回0成功，返回非0失败
 */
static int OlaIsOlaTimeInit(void)
{
    int ret;
    if (g_isInited == false) {
        ret = OlaTimeInit();
        if (ret != OLA_OK) {
            return ret;
        }
        if (g_isInited == false) {
            return OLA_TIME_ERR_TIME_INIT;
        }
    }
    return OLA_OK;
}

/* 时间同步模块注册时间同步回调函数 */
void OlaRegTimeCallback(OlaFirstTimeSyncCallbak func, void *param)
{
    int ret;
    ret = OlaIsOlaTimeInit();
    if (ret != OLA_OK) {
        OlaLogWarning("Ola time init faile:%d", ret);
        return;
    }

    ret = OlaMutexLock(g_timeParams.mutex, 0xFFFFFFFFU);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola mutex lock faile:%d", ret);
        return;
    }

    g_timeParams.func = func;
    g_timeParams.param = param;

    (void)OlaMutexUnLock(g_timeParams.mutex);
}

static void OlaPrintTimeInfo(const OlaTimeInfo *timeInfo)
{
    (void)timeInfo;
    OlaLogNotice("time sync for time, current utc time is:");
    OlaLogNotice("   %u-%d-%d %d:%d:%d", timeInfo->year, timeInfo->month, timeInfo->day,
        timeInfo->hour, timeInfo->min, timeInfo->sec);
}

/*
 * 将ms转换为日期
 * 返回0成功，返回非0失败
 */
static int OlaGetDate(OlaTimeInfo *timeInfo, unsigned long long timeSec)
{
    unsigned int a;
    unsigned int b;
    unsigned int c;
    unsigned int d;
    unsigned int e;
    unsigned int f;

    /* 利用算法转换时间为日期，数字为公式中的常量 */
    a = (unsigned int)((4 * timeSec + 102032) / 146097 + 15);
    b = (unsigned int)(timeSec + 2442113 + a - (a / 4));
    if (b > (UINT_MAX / 20)) {
        return OLA_TIME_ERR_ALGO;
    }
    c = (20 * b - 2442) / 7305;
    d = b - 365 * c - (c / 4);
    if (d > (UINT_MAX / 1000)) {
        return OLA_TIME_ERR_ALGO;
    }
    e = d * 1000 / 30601;
    f = d - e * 30 - e * 601 / 1000;

    /* 一月和二月被当做是前一年的第13和14个月 */
    if (e <= 13) {
        c -= 4716;
        e -= 1;
    } else {
        c -= 4715;
        e -= 13;
    }

    /* 得到年月日 */
    timeInfo->year = (unsigned short)c;
    timeInfo->month = (unsigned char)e;
    timeInfo->day = (unsigned char)f;
    return OLA_OK;
}

/*
 * 时间同步模块转换ms为日期
 * 返回0成功，返回非0失败
 */
static int OlaConvertTime(unsigned long long timeMs, OlaTimeInfo *timeInfo)
{
    int ret;
    unsigned char weekDay;
    unsigned long long timeSec;

    if ((timeInfo == NULL) || (timeMs == 0)) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    timeSec = timeMs;
    /* 计算出小时 分钟和秒 */
    timeInfo->sec = (unsigned char)(timeSec % OLA_SECONDS_PER_MINUTE);
    timeSec = (unsigned int)(timeSec / OLA_SECONDS_PER_MINUTE);
    timeInfo->min = (unsigned char)(timeSec % OLA_MINUTES_PER_HOUR);
    timeSec = (unsigned int)(timeSec / OLA_MINUTES_PER_HOUR);
    timeInfo->hour = (unsigned char)(timeSec % OLA_HOURS_PER_DAY);
    timeSec = (unsigned int)(timeSec / OLA_HOURS_PER_DAY);

    if (OlaGetDate(timeInfo, timeSec) != OLA_OK) {
        OlaLogWarning("Ola get timesec faile");
        return OLA_TIME_ERR_GIT_TIMEINFO;
    }

    ret = OlaGetWeekDayFromDate(timeInfo->year, timeInfo->month, timeInfo->day, &weekDay);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola get week faile");
        return ret;
    }

    timeInfo->wDay = weekDay;
    timeInfo->wDay = (unsigned char)(1 << timeInfo->wDay);

    return OLA_OK;
}

/*
 * 初次时间同步，回调函数通知注册者
 * 返回0成功，返回非0失败
 */
static int OlaSyncFirstTimeFunc(OlaTimeInfo *timeInfo)
{
    if (g_timeParams.timeSynced == false) {
        if (g_timeParams.func != NULL) {
            g_timeParams.func(timeInfo, g_timeParams.param);
        }
    }

    return OLA_OK;
}

/*
 * 时间同步模块设置时间
 * 返回0成功，返回非0失败
 */
int OlaSetTime(unsigned long long utcTimeMs)
{
    int ret;
    unsigned long long curSysTimeMs = 0;

    ret = OlaIsOlaTimeInit();
    if (ret != OLA_OK) {
        OlaLogWarning("Ola time init faile");
        return ret;
    }

    ret = OlaGetTime(&curSysTimeMs);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola get time faile");
        return ret;
    }

    ret = OlaMutexLock(g_timeParams.mutex, 0xFFFFFFFFU);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola mutex lock faile");
        return OLA_TIME_ERR_MUTEX_LOCK;
    }

    /* 如果时区字符串为空，则默认为东八区 */
    g_timeParams.lastSyncUtcTime = utcTimeMs;
    g_timeParams.lastSystemTime = curSysTimeMs;

    /* 打印当前同步的时间 */
    OlaTimeInfo timeInfo = {0};
    (void)memset_s(&timeInfo, sizeof(OlaTimeInfo), 0, sizeof(OlaTimeInfo));
    ret = OlaConvertTime(g_timeParams.lastSyncUtcTime, &timeInfo);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola convert time faile");
        return OLA_TIME_ERR_TIME_CONVERT;
    }
    OlaPrintTimeInfo((const OlaTimeInfo *)&timeInfo);

    ret = OlaSyncFirstTimeFunc(&timeInfo);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola time timesync fail");
        (void)OlaMutexUnLock(g_timeParams.mutex);
        return ret;
    }

    g_timeParams.timeSynced = true;
    (void)OlaMutexUnLock(g_timeParams.mutex);

    return OLA_OK;
}

/*
 * 时间同步模块获取当前UTC时间和时区信息
 * 返回0成功，返回非0失败
 */
int OlaGetUtcTimeMs(unsigned long long *utcTimeMs)
{
    int ret;
    unsigned long long curSysTimeMs = 0;

    if (utcTimeMs == NULL) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    ret = OlaIsOlaTimeInit();
    if (ret != OLA_OK) {
        OlaLogWarning("Ola time init faile");
        return ret;
    }

    ret = OlaMutexLock(g_timeParams.mutex, 0xFFFFFFFFU);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola mutex lock faile");
        return OLA_TIME_ERR_MUTEX_LOCK;
    }

    if (g_timeParams.timeSynced == false) {
        OlaLogWarning("Ola time not timesync");
        (void)OlaMutexUnLock(g_timeParams.mutex);
        return OLA_TIME_ERR_NOT_TIMESYNC;
    }

    ret = OlaGetTime(&curSysTimeMs);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola get time faile");
        return ret;
    }

    unsigned long long deltaTime = OlaDeltaTime(curSysTimeMs, g_timeParams.lastSystemTime);
    ret = OlaIsUtcDeltaTimeOverflow(deltaTime);
    if (ret != OLA_OK) {
        (void)OlaMutexUnLock(g_timeParams.mutex);
        return ret;
    }

    *utcTimeMs = g_timeParams.lastSyncUtcTime + deltaTime;

    (void)OlaMutexUnLock(g_timeParams.mutex);

    return OLA_OK;
}

/*
 * 时间同步模块获取当前UTC日期信息
 * 返回0成功，返回非0失败
 */
int OlaGetUtcTimeInfo(OlaTimeInfo *timeInfo)
{
    int ret;
    unsigned long long curSysTimeMs = 0;
    unsigned long long curTimeMs;

    if (timeInfo == NULL) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    ret = OlaIsOlaTimeInit();
    if (ret != OLA_OK) {
        OlaLogWarning("Ola time init faile");
        return ret;
    }

    ret = OlaMutexLock(g_timeParams.mutex, 0xFFFFFFFFU);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola mutex lock faile");
        return OLA_TIME_ERR_MUTEX_LOCK;
    }

    if (g_timeParams.timeSynced == false) {
        OlaLogWarning("Ola time not timesync");
        (void)OlaMutexUnLock(g_timeParams.mutex);
        return OLA_TIME_ERR_NOT_TIMESYNC;
    }

    ret = OlaGetTime(&curSysTimeMs);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola get time faile");
        return ret;
    }

    unsigned long long deltaTime = OlaDeltaTime(curSysTimeMs, g_timeParams.lastSystemTime);
    ret = OlaIsUtcDeltaTimeOverflow(deltaTime);
    if (ret != OLA_OK) {
        (void)OlaMutexUnLock(g_timeParams.mutex);
        return ret;
    }
    curTimeMs = g_timeParams.lastSyncUtcTime + deltaTime;
    (void)OlaMutexUnLock(g_timeParams.mutex);

    return OlaConvertTime(curTimeMs, timeInfo);
}

/*
 * 重置时间同步模块
 */
int OlaTimeDeinit(void)
{
    int ret;
    /* 关闭定时器的判断条件 */
    ret = OlaControlTimeSyncTimer(OLA_STOP_TIMER);
    if (ret == OLA_OK) {
        g_isInited = false;
        if (g_timeParams.mutex != NULL) {
            (void)OlaMutexDestroy(g_timeParams.mutex);
        }
        (void)memset_s(&g_timeParams, sizeof(g_timeParams), 0, sizeof(g_timeParams));
    }

    return ret;
}

static void OlaSetTimeCallback(unsigned long long timestamp)
{
    int ret = OlaSetTime(timestamp);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola set time fail:%d", ret);
        return;
    }
}

/*
 * 时间同步开启
 * 返回0成功，返回非0失败
 */
int OlaStartSyncTime(void)
{
    int ret;
    unsigned long long curSysTimeMs = 0;

    ret = OlaTimeSync(&curSysTimeMs, (OlaTimeSyncAsynCallbak)OlaSetTimeCallback);
    if (ret == OLA_TIME_SYNC_FAILED) {
        OlaLogWarning("Ola get time faile:%d", ret);
        return OLA_TIME_ERR_TIME_SYNC;
    } else if (ret == OLA_TIME_SYNC_ASYN) {
        OlaLogWarning("Ola get time asyn:%d", ret);
        return OLA_TIME_ERR_TIME_SYNC_ASYN;
    }

    ret = OlaSetTime(curSysTimeMs);
    if (ret != OLA_OK) {
        OlaLogWarning("Ola set time fail");
        return ret;
    }

    return OLA_OK;
}

/*
 * 查询时间同步状态
 * 返回0成功，返回非0失败
 */
bool OlaGetTimeSynced(void)
{
    return g_timeParams.timeSynced;
}

static void OlaStartSyncTimeCallback(const void *context)
{
    (void)context;
    int ret = OlaStartSyncTime();
    // 保持时间同步定时器的开启
    OlaControlTimeSyncTimer(OLA_START_TIMER);
    if (ret != OLA_OK) {
        OlaLogNotice("Ola start asyn synctime timer :%d", ret);
        return;
    }
}

/*
 * 开启或者关闭时间同步定时器
 * 返回0成功，返回非0失败
 * 注意：action 0是关闭 1是开启
 */
int OlaControlTimeSyncTimer(int action)
{
    if ((action != 0) && (action != 1)) {
        OlaLogWarning("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    OlaTimer TimeSyncTimer = {OLA_RECOVERY_LONG_T, OlaStartSyncTimeCallback, NULL};

    if (action == OLA_START_TIMER) {
        OlaStartTimer(&TimeSyncTimer, OLA_TIMESYNC_TIMER);
    } else {
        OlaStopTimer(OLA_TIMESYNC_TIMER);
    }

    return OLA_OK;
}
